// Copyright 2019 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.

package main

import (
	"encoding/hex"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"regexp"
	"sort"

	"github.com/google/syzkaller/pkg/osutil"
)

func main() {
	flag.Parse()
	args := flag.Args()
	if len(args) != 2 {
		usage()
	}

	syslog, err := ioutil.ReadFile(args[0])
	if err != nil {
		failf("failed to read file %v: %v", args[0], err)
	}

	usbIds := extractIds(syslog, "USBID", 34)
	hidIds := extractIds(syslog, "HIDID", 24)

	output := make([]byte, 0)
	output = append(output, []byte("// AUTOGENERATED FILE\n")...)
	output = append(output, []byte("// See docs/linux/external_fuzzing_usb.md\n")...)
	output = append(output, []byte("\n")...)
	output = append(output, []byte("package linux\n")...)
	output = append(output, []byte("\n")...)
	output = append(output, generateIdsVar(usbIds, "usbIds")...)
	output = append(output, []byte("\n")...)
	output = append(output, generateIdsVar(hidIds, "hidIds")...)

	if err := osutil.WriteFile(args[1], output); err != nil {
		failf("failed to output file %v: %v", args[1], err)
	}
}

func extractIds(syslog []byte, prefix string, size int) []string {
	re := fmt.Sprintf("%s: [0-9a-f]{%d}", prefix, size)
	r := regexp.MustCompile(re)
	matches := r.FindAll(syslog, -1)
	uniqueMatches := make(map[string]bool)
	for _, match := range matches {
		uniqueMatches[string(match)] = true
	}
	sortedMatches := make([]string, 0)
	for match := range uniqueMatches {
		match = match[len(prefix+": "):]
		match = match[:size]
		sortedMatches = append(sortedMatches, match)
	}
	sort.Strings(sortedMatches)
	return sortedMatches
}

func generateIdsVar(ids []string, name string) []byte {
	output := []byte(fmt.Sprintf("var %s = ", name))
	for i, id := range ids {
		decodedID, err := hex.DecodeString(id)
		if err != nil {
			failf("failed to decode hex string %v: %v", id, err)
		}
		prefix := "\t"
		suffix := " +"
		if i == 0 {
			prefix = ""
		}
		if i == len(ids)-1 {
			suffix = ""
		}
		outputID := fmt.Sprintf("%v%#v%v\n", prefix, string(decodedID), suffix)
		output = append(output, []byte(outputID)...)
	}

	fmt.Printf("%v %s ids written\n", len(ids), name)

	return output
}

func usage() {
	fmt.Fprintf(os.Stderr, "usage:\n")
	fmt.Fprintf(os.Stderr, "  syz-usbgen syslog.txt sys/linux/init_vusb_ids.go\n")
	os.Exit(1)
}

func failf(msg string, args ...interface{}) {
	fmt.Fprintf(os.Stderr, msg+"\n", args...)
	os.Exit(1)
}
